import { UserAuthRepo, UserDevicesRepo, UserGeoRepo, UserTokenRepo } from '@/entities'
import { CoreException, CoreExceptionType, NRedis } from '@/modules'
import { snowflake, timestampFormat, uuid } from '@/util'
import { ConfigService } from '@donews/nestjs-config'
import { forwardRef, Inject, Injectable } from '@nestjs/common'
import moment from 'moment'
import { chain } from 'mathjs'
import md5 from 'md5'
import { UserService } from './user.service'
import { IBodyUser, ILogin, IUserinfo } from '@/interface'
import { RType } from '@/modules/redis/interface'

@Injectable()
export class UserLoginService {
    constructor(
        private readonly userTokenRepo: UserTokenRepo,
        private readonly userDevicesRepo: UserDevicesRepo,
        private readonly userGeoRepo: UserGeoRepo,
        private readonly userAuthRepo: UserAuthRepo,
        private readonly config: ConfigService,
        private readonly redis: NRedis,
        @Inject(forwardRef(() => UserService))
        private readonly userService: UserService,
    ) {}

    /**
     * @description: 根据devid获取token
     * @param {string} devid
     * @param {string} tokenValue
     * @return {*}
     */
    public async getToken(devid: string, tokenValue?: string) {
        if (tokenValue && !(await this.userTokenRepo.findOne({ tokenValue }))) {
            throw new CoreException(CoreExceptionType.ERROR_CORE_LOGIN_TOKEN_FAIL)
        }
        const token = await this.userTokenRepo.findOne({ devid })
        if (!token) {
            throw new CoreException(CoreExceptionType.ERROR_CORE_LOGIN_DEVID_FAIL)
        }
        const now = moment().format('YYYY-MM-DD HH:mm:ss')
        // 如果上次登录态是30天前的，重新登录
        if (moment(token.tokenTime).isBefore(moment().subtract({ days: 30 }))) {
            throw new CoreException(CoreExceptionType.ERROR_CORE_LOGIN_AFTER_LONG)
        }
        // 如果上次登录态是7天前的，更新登录态时间戳
        if (moment(token.tokenTime).isBefore(moment().subtract({ days: 7 }))) {
            await this.userTokenRepo.update({ devid }, { tokenTime: now })
        }
        const user = await this.userService.getOneWithoutSensitive({ userid: token.userid })
        if (!user) {
            throw new CoreException(CoreExceptionType.ERROR_CORE_LOGIN_TOKEN_FAIL)
        }
        return { user, token: token.tokenValue }
    }

    /**
     * @description: 登录
     * @param {ILogin} option
     * @return {*}
     */
    public async login(option: ILogin & IBodyUser) {
        // const { phone, smsCode, devid, devName, __ip } = option
        const { phone, devid, devName, __ip } = option
        let user = await this.userService.getOne({ phone })
        if (!user) {
            user = await this.userService.updateUserByUserid({
                phone,
            })
        }
        if (user.status === 1) throw new CoreException(CoreExceptionType.ERROR_CORE_LOGIN_USER_LOCKED)
        if (user.status === 2) throw new CoreException(CoreExceptionType.ERROR_CORE_LOGIN_BLACK_LIST)
        // await this.smsService.checkCode({ phone, code: smsCode })
        await this.checkDev(devid, devName, user.userid)
        return await this.upToken(user, devid, __ip)
    }

    /**
     * @description: 检查设备
     * @param {string} devid
     * @param {string} devName
     * @param userid
     * @return {*}
     */
    public async checkDev(devid: string, devName: string, userid: string) {
        const now = timestampFormat()
        const exDev = await this.userDevicesRepo.findOne({ userid, devid })
        if (!exDev) {
            await this.userDevicesRepo.save(
                this.userDevicesRepo.create({
                    userid,
                    devid,
                    devName,
                    ct: now,
                    ut: now,
                }),
            )
        } else {
            exDev.devLogin = chain(exDev.devLogin).add(1).done()
            exDev.ut = now
            await this.userDevicesRepo.save(exDev)
        }
    }

    /**
     * @description: 更新token
     * @param {IUserinfo} user
     * @param {string} devid
     * @param ip
     * @return {*}
     */
    public async upToken(user: IUserinfo, devid: string, ip?: string) {
        const tokenValue = `TOKEN${user.userid}${uuid().toLocaleUpperCase()}`
        const now = timestampFormat()
        const oldTokens = await this.userTokenRepo
            .createQueryBuilder()
            .where('userid=:userid or devid=:devid', { userid: user.userid, devid })
            .getMany()
        for await (const t of oldTokens) {
            await this.redis.del(RType.PREFIX_TOKEN + t.tokenValue)
            await this.userTokenRepo.delete(t.id)
        }
        const authid = snowflake()
        await this.userAuthRepo.save(
            this.userAuthRepo.create({
                authid,
                userid: user.userid,
                devid,
                authIp: ip,
                authAccount: user.phone,
                authStatus: 1,
                authType: 1,
                ct: now,
                ut: now,
            }),
        )
        await this.userTokenRepo.save(
            this.userTokenRepo.create({
                userid: user.userid,
                devid,
                authid,
                tokenValue,
                tokenTime: now,
                ct: now,
                ut: now,
            }),
        )
        return {
            userinfo: await this.setToken(tokenValue, user.userid),
            token: tokenValue,
        }
    }

    /**
     * @description: 设置token缓存
     * @param {string} token
     * @param {string} userid
     * @return {*}
     */
    public async setToken(token: string, userid: string) {
        const userinfo = await this.userService.getOneWithoutSensitive({ userid })
        const ttl = this.config.get('login.ttl', 60 * 60)
        await this.redis.set(RType.PREFIX_TOKEN + token, userinfo, ttl)
        return userinfo
    }

    /**
     * @description: 批量更新token
     * @return {*}
     * @param userids
     */
    public async upTokenUsers(userids: string[]) {
        for await (const userid of userids) {
            const devids = await this.userDevicesRepo.find({ userid })
            for await (const devid of devids.map((d) => d.devid)) {
                const old = await this.userTokenRepo.findOne({ userid, devid })
                if (old) {
                    if (await this.redis.get(RType.PREFIX_TOKEN + old.tokenValue)) {
                        await this.setToken(old.tokenValue, userid)
                    }
                }
            }
        }
    }

    /**
     * @description: 退出登录
     * @param {IBodyUser} option
     * @param {string} devid
     * @param {string} token
     * @return {*}
     */
    public async loginout(option: IBodyUser, devid: string, token: string) {
        if (!token || !devid) {
            throw new CoreException(CoreExceptionType.ERROR_CORE_LOGINOUT_FAIL)
        }
        const userid = option.__user.userid
        await this.redis.del(RType.PREFIX_TOKEN + token)
        await this.userTokenRepo.delete({ userid, devid })
        return {}
    }
}
